﻿using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using LuaInterface;

namespace Framework
{
    public delegate void OnAssetLoaded(AssetItem assetItem);
    /// <summary>
    /// 资源缓存单位
    /// </summary>
    public sealed class AssetItem
    {
        /*
         * ab 文件 用 _assetUrl 加载 ab
         * 非ab文件 用 _fullPath 加载 文件
         */
        
        internal static AssetItem GetNonAbAssetItem(string url, int resType)
        {
            var ret = new AssetItem();
            ret.InitNonAbAssetItem(url, resType);
            return ret;
        }

        internal static AssetItem GetAbAssetItem(string url, bool needLoadFromAb)
        {
            var ret = new AssetItem();
            ret.InitAbAssetItem(url, needLoadFromAb);
            return ret;
        }

        internal static void Recycle(ref AssetItem assetItem)
        {
            if (null == assetItem)
            {
                return;
            }
            assetItem.Reset();
            assetItem = null;
        }

        private string _fullPath;
        public void SetFullPath(string fullPath)
        {
            _fullPath = fullPath;
        }

        public string GetFullPath()
        {
            return _fullPath;
        }
        
        internal void Reset()
        {
            _referenceCount = 0;
            allDependencies.Clear();
            _isLoadFinish = false;
            fileLoadError = null;
            _isDestroyed = false;
            _isAbUnloaded = false;
            _needLoadAssetFromAb = false;
            loadAssetFromAbAction = null;
            _onLoadedHandlers.Clear();
            _assetUrl = null;
            _resPath = null;
            isPersistenUrl = false;
            encryptAbPath = null;
            _itemType = AssetType.NONE;
            _abAsset = null;
            _allAssetNames = null;
            _editorEngineAsset = null;
            _engineAsset = null;
            _dataAsset = null;
            _textAsset = null;
        }
        [LuaInterface.NoToLua]
        public AssetItem()
        {

        }
        #region 计数相关        
        /// <summary>
        /// 引用计数
        /// </summary>
        private int _referenceCount;
        public int ReferenceCount => _referenceCount;
        /// <summary>
        /// 所有的依赖
        /// </summary>
        public List<AssetItem> allDependencies = new List<AssetItem>();
        public void Retain()
        {
            _referenceCount++;
            // Logger.LogWarning("AssetItem.Retain, this = " + this);
        }

        public void Release()
        {
            if (_referenceCount > 0)
            {
                _referenceCount--;
            }
        }

        /// <summary>
        /// 增加一个依赖资源
        /// </summary>
        /// <param name="dependencyItem">依赖资源</param>
        internal void AddDependencyItem(AssetItem dependencyItem)
        {
            allDependencies.Add(dependencyItem);
            dependencyItem.Retain();
        }
        #endregion

        #region 一些临时状态
        private bool _isLoadFinish;
        /// <summary>
        /// 是否已经加载完成,标志着可以调用资源加载完成回调了
        /// 只是标志加载完成了，但是存在失败的情况
        /// </summary>
        public bool IsLoadFinish
        {
            get
            {
                return _isLoadFinish;
            }
            internal set
            {
                _isLoadFinish = value;
            }
        }

        /// <summary>
        /// 文件加载的错误信息
        /// </summary>
        internal string fileLoadError;

        /// <summary>
        /// 是否加载成功了
        /// </summary>
        /// <value></value>
        public bool IsLoadSuccess => (_isLoadFinish && string.IsNullOrEmpty(fileLoadError));

        private bool _isDestroyed;
        /// <summary>
        /// 是否已经销毁了
        /// </summary>
        /// <value></value>
        public bool IsDestroyed => _isDestroyed;

        private bool _isAbUnloaded;

        /// <summary>
        /// ab二进制是否已经释放，音频资源这个字段永远是false
        /// </summary>
        /// <value></value>
        public bool IsAbUnloaded => _isAbUnloaded;

        /// <summary>
        /// 是否需要从ab中把asset加载出来
        /// </summary>
        private bool _needLoadAssetFromAb;
        internal bool NeedLoadAssetFromAb
        {
            get => _needLoadAssetFromAb;
            set => _needLoadAssetFromAb = value;
        }

        /// <summary>
        /// 从ab中加载资源的异步操作
        /// </summary>
        internal AssetBundleRequest loadAssetFromAbAction;
        #endregion

        #region  回调管理
        private List<AssetCallBack> _onLoadedHandlers = new List<AssetCallBack>();
        /// <summary>
        /// 添加资源加载好之后的C#回调
        /// </summary>
        /// <param name="onLoaded">c#回调函数</param>
        public void AddOnAssetLoadedCb(OnAssetLoaded onLoaded)
        {
            if (null == onLoaded)
            {
                Logger.LogError("AssetItem.AddOnAssetLoadedCb, onLoaded can not be null!");
                return;
            }
            AssetCallBack assetCall = AssetCallBack.GetAssetCallBack(onLoaded);
            _onLoadedHandlers.Add(assetCall);
        }

        /// <summary>
        /// 添加资源加载好之后的Lua回调
        /// </summary>
        /// <param name="luaObj">lua对象</param>
        /// <param name="luaCb">lua回调函数</param>
        public void AddLuaOnAssetLoadedCb(LuaTable luaObj, LuaFunction luaCb)
        {
            if (null == luaCb)
            {
                Logger.LogError("AssetItem.AddLuaOnAssetLoadedCb, luaCb can not be null!");
                return;
            }
            AssetCallBack assetCall = AssetCallBack.GetAssetCallBack(luaObj, luaCb);
            _onLoadedHandlers.Add(assetCall);
        }

        /// <summary>
        /// 添加
        /// </summary>
        /// <param name="cb">c#的回调</param>
        /// <param name="luaObj">lua回调对象</param>
        /// <param name="luaCb">lua的回调函数</param>
        public void AddAllOnAssetLoadedCb(OnAssetLoaded cb, LuaTable luaObj, LuaFunction luaCb)
        {
            if (null == cb && null == luaCb)
            {
                //Logger.LogWarning("AssetItem.AddAllOnAssetLoadedCb, luaCb and _cb should not all be null!");
                return;
            }
            if (null != cb)
            {
                AssetCallBack assetCall = AssetCallBack.GetAssetCallBack(cb);
                _onLoadedHandlers.Add(assetCall);
            }
            if (null != luaCb)
            {
                AssetCallBack assetCall = AssetCallBack.GetAssetCallBack(luaObj, luaCb);
                _onLoadedHandlers.Add(assetCall);
            }
        }

        /// <summary>
        /// 删除c#类型的回调函数
        /// </summary>
        /// <param name="_cb">回调函数</param>
        public void RemoveCallBack(OnAssetLoaded _cb)
        {
            int count = _onLoadedHandlers.Count;
            AssetCallBack acb = null;
            for (int idx = count - 1; idx >= 0; idx--)
            {
                acb = _onLoadedHandlers[idx];
                if (acb.ClearCallBack(_cb))
                {
                    _onLoadedHandlers.RemoveAt(idx);
                    AssetCallBack.RecyleAssetCallBack(acb);
                }
            }
        }

        /// <summary>
        /// 删除lua类型的回调函数
        /// </summary>
        /// <param name="luaCb"></param>
        /// <param name="luaObj"></param>
        public void RemoveCallBack(LuaInterface.LuaFunction luaCb, LuaInterface.LuaTable luaObj)
        {
            int count = _onLoadedHandlers.Count;
            AssetCallBack acb = null;
            for (int idx = count - 1; idx >= 0; idx--)
            {
                acb = _onLoadedHandlers[idx];
                if (acb.ClearCallBack(luaObj, luaCb))
                {
                    _onLoadedHandlers.RemoveAt(idx);
                    AssetCallBack.RecyleAssetCallBack(acb);
                }
            }
        }

        /// <summary>
        /// 移除所有的回调函数
        /// </summary>
        internal void ClearAllHandlers()
        {
            _onLoadedHandlers.Clear();
        }

        #endregion

        #region 资源内容
        private string _assetUrl;
        /// <summary>
        /// 实际的资源路径,带多语言目录的,只在文件加载流程中使用此变量
        /// </summary>
        public string AssetUrl => _assetUrl;
        private string _resPath;
        /// <summary>
        /// 策划表中的填的相对路径,不带资源多语言路径，业务逻辑中统一使用此变量
        /// </summary>
        public string ResPath => _resPath;

        /// <summary>
        /// 如果是Persisten，从FrameworkSettings.PersistentResRootDir路径中加载
        /// </summary>
        internal bool isPersistenUrl;

        /// <summary>
        /// 加密后的文件路径 md5 值
        /// </summary>
        internal string encryptAbPath;

        private int _itemType;
        public int ItemType => _itemType;
        private AssetBundle _abAsset;
        private string[] _allAssetNames;
        /// <summary>
        /// ab中所有asset的名字
        /// </summary>
        /// <value></value>
        public string[] AllAssetNames
        {
            get { return _allAssetNames; }
        }
        /// <summary>
        /// ab资源
        /// </summary>    
        internal AssetBundle AbAsset => _abAsset;

        /// <summary>
        /// 编辑器下面直接加载的资源对象
        /// </summary>
        private UnityEngine.Object _editorEngineAsset;
        private UnityEngine.Object _engineAsset;
        /// <summary>
        /// 非ab中加载的音频和图片都放在这里,
        /// 或者ab加载完成之后，预加载的引擎资源
        /// </summary>
        public UnityEngine.Object EngineAsset
        {
            get
            {
                if (ResMgr.Instance.IsFromEditorDir)
                {
                    return _editorEngineAsset;
                }
                return _engineAsset;
            }
        }

        /// <summary>
        /// ab中加载的资源保存
        /// </summary>
        /// <param name="asset"></param>
        internal void SetEngineAssetFromAb(UnityEngine.Object asset)
        {
            _engineAsset = asset;
        }

        /// <summary>
        /// 编辑器加载的资源保存
        /// </summary>
        /// <param name="asset"></param>
        internal void SetEngineAssetFromEditor(UnityEngine.Object asset, Type assetType)
        {
            _editorEngineAsset = asset;
            if (assetType == typeof(UnityEngine.TextAsset))
            {
                UnityEngine.TextAsset obj = (UnityEngine.TextAsset)_editorEngineAsset;
                if (_itemType == AssetType.TEXT)
                {
                    _textAsset = obj.text;
                }
                else
                {
                    _dataAsset = obj.bytes;
                }
                return;
            }
        }

        private byte[] _dataAsset;
        /// <summary>
        /// 二进制资源
        /// </summary>
        public byte[] DataAsset => _dataAsset;

        private string _textAsset;

        /// <summary>
        /// 文本资源
        /// </summary>
        public string TextAsset => _textAsset;
        #endregion

        /// <summary>
        /// 非ab资源专用的构造函数
        /// </summary>
        /// <param name="resPath">资源路径</param>
        /// <param name="type">资源类型</param>
        internal void InitNonAbAssetItem(string resPath, int type)
        {
            _resPath = resPath;
            _assetUrl = resPath;
            _itemType = type;
            _referenceCount = 0;
            fileLoadError = string.Empty;
            _dataAsset = null;
            _engineAsset = null;
            _textAsset = string.Empty;
        }

        /// <summary>
        /// ab资源专用构造函数
        /// </summary>
        /// <param name="resPath">ab路径</param>
        /// <param name="needLoadFromAb"></param>
        internal void InitAbAssetItem(string resPath, bool needLoadFromAb)
        {
            _resPath = resPath;
            var ins = ResMgr.Instance;
            _assetUrl = ins.IsAllToOneAsset(resPath) ? ins.GetAllToOneAbName(resPath) : resPath;
            _itemType = AssetType.BUNDLE;
            encryptAbPath = MD5Util.ComputeMd5(_assetUrl);
            _needLoadAssetFromAb = needLoadFromAb;
            _referenceCount = 0;
            _dataAsset = null;
            _engineAsset = null;
            _textAsset = string.Empty;
        }

        /// <summary>
        /// 使用UnityWebRequest加载资源之后，取出资源，AssetBundle的资源不走这里
        /// </summary>
        /// <param name="webRequest"></param>
        /// <returns>资源解析是否成功</returns>
        internal bool SetAssetByWebRequest(UnityWebRequest webRequest)
        {
            DownloadHandler downloadHandler = webRequest.downloadHandler;
            switch (_itemType)
            {
                case AssetType.TEXTURE:
                    _engineAsset = DownloadHandlerTexture.GetContent(webRequest);
                    if (null != _engineAsset)
                    {
                        return true;
                    }
                    else
                    {
                        Logger.LogWarning("AssetItem.SetAssetByWebRequest, TEXTURE - DownloadHandlerTexture is null! _assetUrl = " + _assetUrl);
                        return false;
                    }
                case AssetType.DATA:
                    DownloadHandlerBuffer dataHandler = (DownloadHandlerBuffer)downloadHandler;
                    _dataAsset = dataHandler.data;
                    if (null != _dataAsset)
                    {
                        return true;
                    }
                    else
                    {
                        Logger.LogWarning("AssetItem.SetAssetByWebRequest, DATA - DownloadHandlerBuffer is null! _assetUrl = " + _assetUrl);
                        return false;
                    }
                case AssetType.TEXT:
                    DownloadHandlerBuffer txtHandler = (DownloadHandlerBuffer)downloadHandler;
                    _textAsset = txtHandler.text;
                    if (null != _textAsset)
                    {
                        return true;
                    }
                    else
                    {
                        Logger.LogWarning("AssetItem.SetAssetByWebRequest, TEXT - DownloadHandlerBuffer is null! _assetUrl = " + _assetUrl);
                        return false;
                    }
                case AssetType.AUDIO:
                    _engineAsset = DownloadHandlerAudioClip.GetContent(webRequest);
                    if (null != _engineAsset)
                    {
                        return true;
                    }
                    else
                    {
                        Logger.LogWarning("AssetItem.SetAssetByWebRequest, AUDIO - DownloadHandlerAudioClip is null! _assetUrl = " + _assetUrl);
                        return false;
                    }
                default:
                    Logger.LogError("AssetItem.SetAssetByWebRequest, can not support assettype = " + _itemType + " _assetUrl = " + _assetUrl);
                    return false;
            }
        }

        /// <summary>
        /// ab资源从内存中加载之后，得到ab对象
        /// </summary>
        /// <param name="webRequest"></param>
        /// <returns>资源解析是否成功</returns>
        internal void SetAbAssetFromStream(AssetBundle assetBundle)
        {
            _abAsset = assetBundle;
            _allAssetNames = _abAsset.GetAllAssetNames();
        }
        /// <summary>
        /// 资源加载成功之后的调用
        /// </summary>
        internal void OnLoadedDone()
        {
            if (null != _onLoadedHandlers)
            {
                foreach (AssetCallBack callBack in _onLoadedHandlers)
                {
                    callBack.Invoke(this);
                }
            }
            ClearAllHandlers();
        }


        /// <summary>
        /// 获取资源单位中的引擎资源对象
        /// </summary>
        /// <param name="resName">传空的话，则取资源单位路径对应的资源，取不到，则拿ab中第一个资源</param>
        /// <param name="resType">引擎资源对象类型</param>
        /// <returns>引擎资源对象</returns>
        public UnityEngine.Object GetResource(string resName = null, Type resType = null)
        {
            UnityEngine.Object obj = DoGetResource(resName, resType);
            if (null != obj)
            {
                int refCount = ResMgr.Instance.GetAbRefCount(_assetUrl);
                if (refCount < 1)
                {
                    UnLoadAssetBundle();
                }
            }
            return obj;
        }

        /// <summary>
        /// 真正去获取资源
        /// </summary>
        /// <param name="resName"></param>
        /// <param name="resType"></param>
        /// <returns></returns>
        private UnityEngine.Object DoGetResource(string resName = null, Type resType = null)
        {
            if (!IsLoadSuccess)
            {
                return null;
            }
            //直接加载，则直接返回
            // if (ResMgr.Instance.IsFromEditorDir)
            // {
            //     return _editorEngineAsset;
            // }
            //不是ab加载
            if (_itemType != AssetType.BUNDLE)
            {
                return _engineAsset;
            }

            //只有一个asset的ab需要缓存一下load过的asset
            if (_allAssetNames.Length == 1 && null != _engineAsset)
            {
                return _engineAsset;
            }

            if (_abAsset != null)
            {
                if (string.IsNullOrEmpty(resName))
                {
                    //不传asset的name,默认取第一个asset";
                    resName = _allAssetNames[0];
                }
                else
                {
                    string realResName = resName;
                    resName = FrameworkSettings.GetAssetNameInBundle(realResName);
                }

                if (!_abAsset.Contains(resName))
                {
                    Logger.LogError($"AssetItem.DoGetResource, _abAsset does't contains {resName}, {this} ");
                    _engineAsset = null;
                    return null;
                }
                try
                {
                    _engineAsset = (resType == null) ? _abAsset.LoadAsset(resName) : _abAsset.LoadAsset(resName, resType);
                    return _engineAsset;
                }
                catch (System.Exception exp)
                {
                    Logger.LogException(exp);
                    Logger.LogError($"AssetItem.DoGetResource, can not load {resName} from _abAsset, Exception: {exp}");
                    _engineAsset = null;
                    return null;
                }
            }
            Logger.LogWarning($"AssetItem.DoGetResource, _abAsset is not loaded! {this} ");
            return null;
        }

        /// <summary>
        /// 异步获取资源单位中的引擎资源对象
        /// </summary>
        /// <param name="resName">加载完成后的回调</param>
        /// <param name="resName">传空的话，则取资源单位路径对应的资源，取不到，则拿ab中第一个资源</param>
        /// <param name="resType">引擎资源对象类型</param>
        [NoToLua]
        public void GetResourceAsync(System.Action<UnityEngine.Object> callBack, string resName = null, Type resType = null)
        {
            AssetBundleLoader.Instance.LoadAssetFromAb(this, (assetItem, obj) =>
            {
                callBack?.Invoke(obj);
            }, resName, resType);
        }

        /// <summary>
        /// 异步获取资源单位中的引擎资源对象
        /// </summary>
        /// <param name="resName">加载完成后的回调</param>
        /// <param name="resName">传空的话，则取资源单位路径对应的资源，取不到，则拿ab中第一个资源</param>
        /// <param name="resType">引擎资源对象类型</param>
        /// <returns>引擎资源对象</returns>
        public void GetResourceAsync(LuaFunction luaCb, LuaTable luaObj, string resName = null, Type resType = null)
        {
            AssetBundleLoader.Instance.LoadAssetFromAb(this, (assetItem, obj) =>
            {
                if (null == luaCb)
                {
                    return;
                }
                if (null != luaObj)
                {
                    luaCb.Call(luaObj, obj);
                }
                else
                {
                    luaCb.Call(obj);
                }
            }, resName, resType);
        }

        /// <summary>
        /// 获取资源缓存单位中的所有的引擎资源对象
        /// 一般用于ab资源的加载
        /// 单个资源的获取使用GetResource接口
        /// </summary>
        /// <returns>所有引擎资源对象</returns>
        public UnityEngine.Object[] GetAllResources()
        {
            if (!IsLoadSuccess)
            {
                return null;
            }
            if (_itemType == AssetType.BUNDLE)
            {
                if (!ResMgr.Instance.IsFromEditorDir && null != _abAsset)
                {
                    return _abAsset.LoadAllAssets();
                }
            }
            UnityEngine.Object res = GetResource();
            if (null != res)
            {
                return new UnityEngine.Object[] { res };
            }
            return null;
        }

        /// <summary>
		/// 获取非ab加载的文本资源
		/// </summary>
		/// <param name="needVerify">是否需要验证资源完整性</param>
		/// <returns></returns>
		public string GetNonAbTextAsset(bool needVerify = false)
        {
            if (needVerify)
            {
                byte[] data = FileHelper.RsaVerity(DataAsset);
                return StringHelper.ByteArray2Str(data);
            }
            return TextAsset;
        }

        public override string ToString()
        {
            if (_itemType == AssetType.BUNDLE)
            {
                return string.Format(" [assetUrl:{0} key:{1} type:{2} refCnt:{3} unloaded:{4}] ", _assetUrl, encryptAbPath, _itemType, _referenceCount, _isAbUnloaded);
            }
            return string.Format(" [assetUrl:{0} type:{1} refCnt:{2} unloaded:{3}] ", _assetUrl, _itemType, _referenceCount, _isAbUnloaded);
        }

        /// <summary>
        /// 卸载ab二进制内存，但是不释放其加载出来的资源对象
        /// </summary>
        internal void UnLoadAssetBundle()
        {
            if (_abAsset && !_isAbUnloaded && CanUnloadAb())
            {
                // Logger.Log("UnLoadAssetBundle " + this.ToString());
                _abAsset.Unload(false);
                //就算这里不赋值为空,Unload的调用也会把它置空
                _abAsset = null;
                _isAbUnloaded = true;
            }
        }

        /// <summary>
        /// 只包含一个asset的ab才unload(false)
        /// </summary>
        private bool CanUnloadAb()
        {
            return _abAsset != null && _allAssetNames.Length == 1;
        }

        /// <summary>
        /// 完全销毁这个资源，
        /// 包括卸载ab内存，ab加载出来的资源对象，解除资源单位对其他依赖资源单位的引用
        /// 非ab资源的话，则是销毁读取到的资源对象
        /// </summary>        
        internal void DestroySelf()
        {
            if (_isDestroyed)
            {
                return;
            }
            _isDestroyed = true;

            foreach (AssetItem dep in allDependencies)
            {
                dep.Release();
            }
            allDependencies.Clear();

            if (null != _abAsset && !_isAbUnloaded)
            {
                _abAsset.Unload(true);
                UnityEngine.Object.Destroy(_abAsset);
                _abAsset = null;
            }
            if (null != _engineAsset)
            {
#if UNITY_EDITOR
                UnityEngine.Object.DestroyImmediate(_engineAsset, true);
#else
                UnityEngine.Object.Destroy(_engineAsset);

#endif
                _engineAsset = null;
            }
#if UNITY_EDITOR
            _editorEngineAsset = null;
#endif

            _dataAsset = null;
            _textAsset = null;
        }
    }
}
